//
//  Session.m
//  HelloWorld
//
//  Created by Martin Le C. on 5/13/09.
//  Copyright 2009 __MyCompanyName__. All rights reserved.
//
#import "Session.h"
#import "MVDirectClientConnection.h"
#import <arpa/inet.h>
#define MVAssertMainThreadRequired() \
NSAssert1( pthread_main_np(), @"Method needs to run on the main thread, not %@.", [NSThread currentThread] )

#define MVAssertCorrectThreadRequired(thread) \
NSAssert2( [NSThread currentThread] == (thread), @"Method needs to run on %@, not %@.", (thread), [NSThread currentThread] )

#define MVInline static __inline__ __attribute__((always_inline))
@implementation Session
@synthesize primaryID;				
@synthesize loginID;					
@synthesize password;				
@synthesize cookieY;
@synthesize cookieT;									
@synthesize status;					
@synthesize customStatusMessage;		
@synthesize customStatusBusy;		
@synthesize groups;		
@synthesize identities;				
// -----Session
@synthesize sessionStatus;				
@synthesize sessionId;								
// -----I/O
@synthesize network;		
// -----Threads
@synthesize userStore;			
@synthesize pingThreadQuit;
@synthesize pingThread;
//@synthesize typingNotifiers;		
// -----Login
@synthesize loginOver;							
@synthesize chatID;
@synthesize cachePacket;
- (id)init
{
	return [self initWithDelegate:nil];
}
-(id)initWithDelegate: (id)delegate {
	status = STATUS_AVAILABLE;
	theDelegate = delegate;
	cachePacket = [YMSG9Packet alloc];
	identities = [NSMutableArray array];
	groups = [[NSMutableArray alloc] init];
	sessionId = 0; 
	sessionStatus = UNSTARTED;
	loginOver = NO;
	pingThreadQuit = NO;
	if( [network connectionThread] ) return 0;
	network = [[MVDirectClientConnection allocWithZone:nil] init];
	[network setDelegate:self];
	return self;
}
- (id)delegate {
	return theDelegate;
}

- (void)setDelegate:(id)delegate {
	theDelegate = delegate;
}
// Call this to connect to the Yahoo server and do all the initial
// handshaking and accepting of data
// u - Yahoo id
// p - password
-(void)login:(NSString*)u p:(NSString*)p {
	@synchronized (self) {
		// -----Check the session status first
		if(self.sessionStatus!=UNSTARTED) {
			NSException *exception = [NSException exceptionWithName:@"LoginException" reason:@"Session should be unstarted"  userInfo:nil];
			@throw exception;
		}
		// -----Yahoo ID's are apparently always lower case
		u = [u lowercaseString];
		// -----Reset session and init some variables
		[self resetData];
		self.loginID	= u;  
		self.primaryID	= nil;  
		self.password	= p;
		self.sessionId	= 0; 
		NSLog(@"time out:%@", u);
		// -----Open the socket
		[self.network connectToHost:DIRECT_HOST_DEFAULT onPort:5050];
		// -----Create the socket input thread, begin the login process and
		// -----wait politely for its conclusion
		//[self startThreads];
		//[self transmitAuth];
		// -----Wait until connection or timeout
		UInt32 timeout =[NSDate timeIntervalSinceReferenceDate]+LOGIN_TIMEOUT;
		while(!(self.loginOver) && ![self past:timeout]) {
			@try { 
				[NSThread sleepForTimeInterval:0.01]; 
			} 
			@catch(NSException* e) {
				NSLog(@"exception reason: %@",[e reason]);
			}
		}
		// -----Check for failure
		if([self past:timeout])
		{	
			self.sessionStatus = FAILED;  
			[self closeNetwork];
			if([theDelegate respondsToSelector:@selector(Session:errorLogin:)])
			{
				[theDelegate Session:self errorLogin:@"Time out! No responding from servers"];
			}
		}
		
	}	
}
- (void)directClientConnection:(MVDirectClientConnection *) connection didConnectToHost:(NSString *) host port:(unsigned short) port {
	[self transmitAuth];
}
-(void)logout{
	@synchronized (self) {
		[self checkStatus];  
		self.sessionStatus=UNSTARTED;
		[self transmitLogoff];
	}
}
// -----------------------------------------------------------------
// Reset a failed session, so the session object can be used again
// (for all those who like to minimise the number of discarded objects
// for the GC to clean up  ;-)
// -----------------------------------------------------------------
-(void)reset {
	@synchronized (self) {
		if(self.sessionStatus!=FAILED && self.sessionStatus!=UNSTARTED) {
			NSException* exception = [NSException exceptionWithName:@"IllegalState" reason:@"Session currently active" userInfo:nil];
			@throw exception;
		}
		self.sessionStatus=UNSTARTED;  
		[self resetData];	// Just to be on the safe side
	}
}
// -----------------------------------------------------------------
// Send a message
// to - the Yahoo ID to transmit to
// msg - message text
// -----------------------------------------------------------------
-(void)sendMessage:(NSString*)to msg:(NSString*)msg {	
	//[self checkStatus];  
	[self transmitMessage:to yid:self.loginID msg:msg];
}
-(void) sendMessage:(NSString*)to msg:(NSString*)msg yid:(YahooIdentity*)yid {	
	//[self checkStatus];  
	[self checkIdentity:yid];  
	[self transmitMessage:to yid:yid.yahooId msg:msg];
}
// -----------------------------------------------------------------
// Send a >>buzz<< message
// -----------------------------------------------------------------
-(void)sendBuzz:(NSString*)to {	
	[self sendMessage:to msg:BUZZ];		// Buzz defined in NetworkConstants
}
-(void)sendBuzz:(NSString*)to yid:(YahooIdentity*)yid {	
	[self sendMessage:to msg:BUZZ yid:yid];	// Buzz defined in NetworkConstants
}
// -----------------------------------------------------------------
// Ask server to return refreshed stats for this session.  Server will 
// send back a USERSTAT and truncated NEWMAIL packet.
// -----------------------------------------------------------------
-(void)refreshStats{
	@try {
		[self checkStatus];  
		[self transmitUserStat];
	}
	@catch (NSException* e) {
		
	}
}
// -----------------------------------------------------------------
// Transmit a USERSTAT packet.  Purpose?  Unknown  :-)
// It would seem that transmiting this packet results in a USERSTAT
// (0x0a) packet followed by a NEWMAIL (0x0b) packet with just the
// 9 field (new mail count) set being sent by the server.
// -----------------------------------------------------------------
-(void)transmitUserStat								// 0x0a
{	
	NSLog(@"testing");
	PacketBodyBuffer* body = [[PacketBodyBuffer alloc] init];
	[body addElement:@"0" value:self.loginID];
	NSLog(@"testing");
	[self sendPacket:body service:SERVICE_USERSTAT];
	[body release];
}
-(YahooIdentity*)getPrimaryIdentity { 
	return [self identityIdToObject:self.primaryID]; 
}
-(YahooIdentity*)getLoginIdentity { 
	return [self identityIdToObject:self.loginID]; 
}
-(void)activateIdentity:(YahooIdentity*)yid activate:(BOOL)activate {
	[self checkStatus];  
	[self checkIdentity:yid];
	// -----Make an exception of the primary identity
	if([yid.yahooId isEqualToString:[self primaryID]]) {
		NSException* exception = [NSException exceptionWithName:@"IllegalArgument" reason:@"Primary identity cannot be de/activated" userInfo:nil];
		@throw exception;
	}
	// -----Send message
	if(activate)  
		[self transmitIdActivate:yid.yahooId];
	else  
		[self transmitIdDeactivate:yid.yahooId];
}
// -----------------------------------------------------------------
// Transmit an AUTH packet, as a way of introduction to the server.
// As we do not know our primary ID yet, both 0 and 1 use loginID .
// -----------------------------------------------------------------
-(void)transmitAuth {	
	self.sessionStatus=AUTH;								// Set status
	PacketBodyBuffer* body = [[PacketBodyBuffer alloc] init]; 
	//[body addElement:@"0" value:self.loginID];		// FIX: only req. for HTTPConnectionHandler ? But I don't use HTTP
	[body addElement:@"1" value:self.loginID];
	[self sendPacket:body service:SERVICE_AUTH];					// 0x57
	[body release];
}

// -----------------------------------------------------------------
// Transmit an AUTHRESP packet, the second part of our login process.
// As we do not know our primary ID yet, both 0 and 1 use loginID .
// Note: message also contains our initial status.
// plp - plain response (not MD5Crypt'd)
// crp - crypted response (MD5Crypt'd)
// -----------------------------------------------------------------
-(void)transmitAuthResp:(NSString*)s {	
	PacketBodyBuffer* body = [[PacketBodyBuffer alloc] init];
	[body addElement:@"1" value:self.loginID];
	[body addElement:@"0" value:self.loginID];
	[body addElement:@"277" value:self.cookieY];
	[body addElement:@"278" value:self.cookieT];
	[body addElement:@"307" value:s];	
	[body addElement:@"244" value:@"16777215"];
	[body addElement:@"2" value:self.loginID];
	[body addElement:@"2" value:@"1"];
	[body addElement:@"98" value:@"us"];						
	[body addElement:@"135" value:CLIENT_VERSION];
	[body addElement:@"148" value:@"300"];
	[self sendPacket:body service:SERVICE_AUTHRESP status:self.status];			// 0x54	
	[body release];
}
// -----------------------------------------------------------------
// Transmit a CHATPM packet.  Person message packets.
// ************ DOES THIS WORK ???
// -----------------------------------------------------------------
-(void)transmitChatPM:(NSString*)to msg:(NSString*)msg {	
	PacketBodyBuffer* body = [[PacketBodyBuffer alloc] init];
	[body addElement:@"1" value:to];
	[body addElement:@"5" value:to];
	[body addElement:@"14" value:msg];
	[body addElement:@"97" value:@"1"];
	[body addElement:@"63" value:@";0"];
	[body addElement:@"64" value:@"0"];
	[body addElement:@"241" value:@"0"];
	
	[self sendPacket:body service: SERVICE_CHATPM];		//0x020
	[body release];
}

// -----------------------------------------------------------------
// Transmit a CHATPING packet.
// -----------------------------------------------------------------
-(void)transmitChatPing {	
	PacketBodyBuffer* body = [[PacketBodyBuffer alloc] init];
	[self sendPacket:body service: SERVICE_CHATPING];		//0xa1
	[body release];
}
// -----------------------------------------------------------------
// Transmit a GROUPRENAME packet, to change the name of one of our
// friends groups.
// -----------------------------------------------------------------
-(void)transmitGroupRename:(NSString*)oldName newName:(NSString*)newName {	
	PacketBodyBuffer* body = [[PacketBodyBuffer alloc] init];
	[body addElement:@"1" value:self.primaryID]; 	// effective id?
	[body addElement:@"65" value:oldName];
	[body addElement:@"67" value:newName];
	[self sendPacket:body service: SERVICE_GROUPRENAME];				// 0x13
	[body release];
}
// -----------------------------------------------------------------
// Transmit a IDACT packet.
// -----------------------------------------------------------------
-(void)transmitIdActivate:(NSString*)Id {
	PacketBodyBuffer* body = [[PacketBodyBuffer alloc] init];
	[body addElement:@"3" value:Id];
	[self sendPacket:body service: SERVICE_IDACT];						// 0x07
	[body release];
}

// -----------------------------------------------------------------
// Transmit a IDDEACT packet.
// -----------------------------------------------------------------
-(void)transmitIdDeactivate:(NSString*)Id {
	PacketBodyBuffer* body = [[PacketBodyBuffer alloc] init];
	[body addElement:@"3" value:Id];
	[self sendPacket:body service: SERVICE_IDACT];						// 0x08
	[body release];
}
// -----------------------------------------------------------------
// Transmit a LIST packet.
// -----------------------------------------------------------------
-(void)transmitList {
	PacketBodyBuffer* body = [[PacketBodyBuffer alloc] init];
	[body addElement:@"1" value:self.primaryID];
	[self sendPacket:body service:SERVICE_LIST]; // 0x55
	[body release];
}

// -----------------------------------------------------------------
// Transmit a LOGOFF packet, which should exit us from Yahoo IM.
// -----------------------------------------------------------------
-(void)transmitLogoff {
	PacketBodyBuffer* body = [[PacketBodyBuffer alloc] init];
	[body addElement:@"0" value:self.loginID];
	[self sendPacket:body service:SERVICE_LOGOFF]; // 0x02
	[body release];
}
- (void) sendMessage2:(NSString*)from to:(NSString*)to msg:(NSString*)msg {
	self.sessionStatus=MESSAGING;
	NSMutableArray* msgObject = [[NSMutableArray alloc] init];
	NSLog(@"from: %@to: %@ msg:%@",from, to, msg);
	[msgObject addObject:from];
	[msgObject addObject:to];
	[msgObject addObject:msg];
	[self performSelector:@selector(transmitMessage2:) onThread:[network connectionThread] withObject:msgObject waitUntilDone:NO];
	//[self performSelector:@selector(transmitMessage2:) withObject:msgObject afterDelay:0];
}
// -----------------------------------------------------------------
// Transmit a MESSAGE packet.
// to - the Yahoo ID of the user to send the message to
// yid - Yahoo identity
// msg - the text of the message
// -----------------------------------------------------------------
-(void)transmitMessage2:(id)target {	
	// -----Send packet
	PacketBodyBuffer* body = [[PacketBodyBuffer alloc] init];
	[body addElement:@"1" value:[NSString stringWithFormat:@"%@",[target objectAtIndex:0]]]; // From (primary ID)
	NSLog(@"fucking: %@",self.loginID);
	[body addElement:@"5" value:[NSString stringWithFormat:@"%@",[target objectAtIndex:1]]];
	[body addElement:@"14" value:[NSString stringWithFormat:@"%@",[target objectAtIndex:2]]];
	[body addElement:@"97" value:@"1"];
	[body addElement:@"63" value:@";0"]; 
	[body addElement:@"64" value:@"0"];
	[body addElement:@"241" value:@"0"];
	[self sendPacket:body service:SERVICE_MESSAGE status:STATUS_OFFLINE]; // 0x06
	[body release];
}
// -----------------------------------------------------------------
// Transmit a MESSAGE packet.
// to - the Yahoo ID of the user to send the message to
// yid - Yahoo identity
// msg - the text of the message
// -----------------------------------------------------------------
-(void)transmitMessage:(NSString*)to yid:(NSString*)yid msg:(NSString*)msg {	
	// -----Send packet
	PacketBodyBuffer* body = [[PacketBodyBuffer alloc] init];
	[body addElement:@"1" value:[NSString stringWithFormat:@"%@", yid]]; // From (primary ID)
	NSLog(@"%@",[NSString stringWithFormat:@"%@", yid]);
	[body addElement:@"5" value:to];
	[body addElement:@"14" value:msg];
	[body addElement:@"97" value:@"1"];
	[body addElement:@"63" value:@";0"]; //imvironment....Sorry I can not...write more
	[body addElement:@"64" value:@"0"];
	[body addElement:@"206" value:@"0"];
	[self sendPacket:body service:SERVICE_MESSAGE status:MESSAGING]; // 0x06
	[body release];
}
// -----------------------------------------------------------------
// Transmit a PING packet.
// -----------------------------------------------------------------
-(void)transmitPing
{	
	PacketBodyBuffer* body = [[PacketBodyBuffer alloc] init];
	[self sendPacket:body service:SERVICE_PING]; // 0x12
	[body release];
}
// -----------------------------------------------------------------
// A key 16 was received, send an error message event
// -----------------------------------------------------------------
-(void)errorMessage:(YMSG9Packet*)pkt m:(NSString*)m
{	
	if(m==nil)  
		m= [pkt getValue:@"16"];
}

// -----------------------------------------------------------------
// Process an incoming IDACT packet.
// -----------------------------------------------------------------
-(void)receiveIdAct:(YMSG9Packet*) pkt			// 0x07
{	
	
}

// -----------------------------------------------------------------
// Process an incoming IDDEACT packet.
// -----------------------------------------------------------------
-(void) receiveIdDeact:(YMSG9Packet*) pkt			// 0x08
{	// Fix: do something here!-- I don't know how to do it...hu hu my last test is to crazy...HUT is f..
}
// -----------------------------------------------------------------
// Process and incoming LIST packet.  We'll typically get one of these
// when our logon is sucessful.
// -----------------------------------------------------------------
-(void) receiveList:(YMSG9Packet*)pkt				// 0xf1
{	
	NSMutableArray* list = [[NSMutableArray alloc] init];
	YahooGroup* yg = [[YahooGroup alloc] init];
	YahooUser* u = [[YahooUser alloc] init];
	yg = nil;
	u = nil;
	int j =0;
	//int getOnlyBuddies;
	@try {	
		int count = [pkt.body count];
		for (int i = 0; i < count; i+=2 ) {
			if ((i+1) > count)
				break;
			switch ([[pkt.body objectAtIndex:i] intValue]) {
				case 302: // start list
					switch ([[pkt.body objectAtIndex:i+1] intValue]) {
						case 315:
							// buddy starts - I will add them in the future he he :) 
							break;
						case 318:
							// buddy Groups
							//YahooGroup* yg = [[YahooGroup alloc] init:];
						case 319:
							//Buddies
							break;
						case 320:
							// Ignores ...I will add later - I like..HUT
							break;
							
					}
					break;
				case 303:
					// end a list
					break;
				case 300: // start item
					switch ([[pkt.body objectAtIndex:i+1] intValue]) {
						case 315:
							// buddy starts - I will add them in the future he he :) 
							break;
						case 318:
							// buddy Group	
						case 319:
							//Buddy
							break;
						case 320:
							// Ignore ...I will add later - I like..HUT
							break;
							
					}
					break;
				case 301:
					// end of a item
					break;
				case 65:
					// Group Name
					[list addObject:[[YahooGroup alloc] init:[pkt.body objectAtIndex:i+1]]];
					j++;
					//[list addObject:[[YahooGroup alloc] init:[pkt.body objectAtIndex:i+1]]];
					break;
				case 7:
					// Buddy ID
					yg = [list objectAtIndex:j-1];
					[yg addUser:[[YahooUser alloc] init:[pkt.body objectAtIndex:i+1]]];
					[list replaceObjectAtIndex:j-1 withObject:yg];
					//[list addObject:[[YahooUser alloc] init:[pkt.body objectAtIndex:i+1]]];
					//NSLog(@"total: %i", [list count]);
					break;
				case 10:
					//awayState
					break;
				case 19:
					//AwayText
					break;
				default:
					break;
			}
		}
		[yg release];
		[u release];
		self.groups = list;
		NSLog(@"total:%i", [list count]);
		if([theDelegate respondsToSelector:@selector(Session:didReceivedBuddyList:)])
		{
			[theDelegate Session:self didReceivedBuddyList:YES];
		}
		/*if ([self.groups count])
			[self transmitUserStat];*/
		
	}
	@catch(NSException* e) 
	{ 
		@throw e;
	}
	// -----If this was sent outside the login process is over, send an event (fire event)
}

// -----------------------------------------------------------------
// Process an incoming LOGOFF packet.  If we get one of these it means
// Yahoo wants to throw us off the system.  When logging in using the
// same Yahoo ID using a second client, I noticed the Yahoo server sent
// one of these (just a header, no contents) and closed the socket.
// -----------------------------------------------------------------
-(void)receiveLogoff:(YMSG9Packet*)pkt			// 0x02
{	// -----Is this packet about us, or one of our online friends?
	if(![pkt exists:@"7"])					// About us
	{	// -----Note: when this method returns, the input thread loop
		// -----which called it exits.
		self.sessionStatus = UNSTARTED;  
	} else									// About friends
	{	// -----Process optional section, friends going offline
		@try
		{	
			[self updateFriendsStatus:pkt];
		}
		@catch(NSException* e) {
			NSException* exception = [NSException exceptionWithName:@"LoginException" reason:@"online friends in logoff" userInfo:nil];
			@throw exception;
		}
	}
}
// -----------------------------------------------------------------
// Transmit an ISAWAY packet.  To return, try transmiting an ISBACK
// packet!  Comes in two flavours: custom message and regular
// -----------------------------------------------------------------
-(void)transmitIsAway {
	PacketBodyBuffer* body = [[PacketBodyBuffer alloc] init];
	[body addElement:@"10" value:[NSString stringWithFormat:@"%i",self.status]]; 
	[self sendPacket:body service:SERVICE_ISAWAY status:self.status];						// 0x03
	[body release];
}

-(void)transmitIsAway:(NSString*)msg a:(BOOL)a {	
	PacketBodyBuffer* body = [[PacketBodyBuffer alloc] init];
	self.status=STATUS_CUSTOM;
	[body addElement:@"10" value:[NSString stringWithFormat:@"%i",self.status]];
	[body addElement:@"19" value:msg];
	if (a) 
		[body addElement:@"47" value:@"1"]; // 1=away
	else 
		[body addElement:@"19" value:@"0"]; //0=back
	[self sendPacket:body service:SERVICE_ISAWAY status:self.status];						// 0x03
	[body release];
}

// -----------------------------------------------------------------
// Transmit an ISBACK packet, contains no body, just the Yahoo status.
// We should send this to return from an ISAWAY, or after we have
// confirmed a sucessful LOGON - it sets our initial status (visibility)
// to the outside world.  Typical initial values for 'status' are
// AVAILABLE and INVISIBLE.
// -----------------------------------------------------------------
-(void)transmitIsBack {	
	PacketBodyBuffer* body = [[PacketBodyBuffer alloc] init];
	self.status=STATUS_CUSTOM;
	[body addElement:@"10" value:[NSString stringWithFormat:@"%i",self.status]];
	[self sendPacket:body service:SERVICE_ISAWAY status:self.status]; // 0x04
	[body release];
}
// -----------------------------------------------------------------
// Process an incoming LOGON packet.  If we get one of these it means
// the logon process has been successful.  If the user has friends
// already online, an extra section of varying length is appended,
// starting with a count, and then detailing each friend in turn.
// -----------------------------------------------------------------
-(void)receiveLogon:(YMSG9Packet*) pkt			// 0x01
{	// -----Is this packet about us, or one of our online friends?
	if([pkt exists:@"7"])
	{	// -----Process optional section, friends currently online
		@try {	
			[self updateFriendsStatus:pkt];
		}
		@catch (NSException* e) 
		{ 
			NSException* exception = [NSException exceptionWithName:@"LoginException" reason:@"online friends in logon" userInfo:nil];
			@throw exception;
		}
	}
	// -----Still logging in?
	if(!self.loginOver)
	{	
		@try {
			if((UInt32)self.status==STATUS_AVAILABLE)  
				[self transmitIsBack];
			else  
				[self transmitIsAway];
		}
		@catch(NSException* e) {
			NSLog(@"exception reason:", [e reason]);
		}
		self.sessionStatus=MESSAGING;  
		self.loginOver=YES;
	}
}

// -----------------------------------------------------------------
// Process an incoming MESSAGE packet.  Message can be either online
// or offline, the latter having a datestamp of when they were sent.
// -----------------------------------------------------------------
-(void)receiveMessage:(YMSG9Packet*) pkt			// 0x06
{	
	@try {	
		if(![pkt exists:@"14"])					// Contains no message?
		{	
			return;
		} else if(pkt.status == STATUS_NOTINOFFICE)	{ //// Sent while we were offline	
			int i=0;
			// -----Read each message, until null
			NSString* s = [pkt getNthValue:@"31" n:i];
			while(s!=nil)
			{	
				NSLog(@"from: %@ to: %@ content: %@", [pkt getValue:@"4"], [pkt getValue:@"5"], [pkt getValue:@"14"]);
				if([theDelegate respondsToSelector:@selector(Session:didReceivedPM:to:msg:)])
				{
					[theDelegate Session:self didReceivedPM:[pkt getValue:@"4"] to:[pkt getValue:@"5"] msg:[pkt getValue:@"14"]];
				}
				i++;  
				s= [pkt getNthValue:@"31" n:i];
			}
		}
		else					
		{	
			
			NSLog(@"from: %@ to: %@ content: %@", [pkt getValue:@"4"], [pkt getValue:@"5"], [pkt getValue:@"14"]);
			if([theDelegate respondsToSelector:@selector(Session:didReceivedPM:to:msg:)])
			{
				[theDelegate Session:self didReceivedPM:[pkt getValue:@"4"] to:[pkt getValue:@"5"] msg:[pkt getValue:@"14"]];
			}
		}
	} @catch(NSException* e) {
		NSException* exception = [NSException exceptionWithName:@"LoginException" reason:[e reason] userInfo:nil];
		@throw exception;
	}
}
// -----------------------------------------------------------------
// Process and incoming PING packet. It contains only two fields,
// 143 and 144.
// -----------------------------------------------------------------
-(void)receivePing:(YMSG9Packet*)pkt				// 0x12
{
}

// -----------------------------------------------------------------
// Process and incoming USERSTAT packet.
// -----------------------------------------------------------------
-(void)receiveUserStat:(YMSG9Packet*)pkt			// 0x0a
{	
	self.status = pkt.status;
}
// Process Buddy Status Packet
-(void)receiveBudStat: (YMSG9Packet*)pkt			//0xc6  
{
	NSString* buddyID = [NSString alloc];
	NSString* custommsg = [NSString alloc];
	buddyID = [pkt getValue:@"7"];
	NSInteger stat = [[pkt getValue:@"10"] intValue];
	if ([pkt getValue:@"19"]) {
		custommsg = [pkt getValue:@"19"];
	}
	for (int i=0; i<[self.groups count]; i++) {
		if ([[self.groups objectAtIndex:i] isKindOfClass:[YahooUser class]]) {
			if ([[self.groups objectAtIndex:i] yahooId] == buddyID) {
				[[self.groups objectAtIndex:i] setStatus:stat];
				if (custommsg) 
					[[self.groups objectAtIndex:i] setCustomStatusMessage:custommsg];
				break;
			}
		}
	}
	[custommsg release];
	[buddyID release];
	if([theDelegate respondsToSelector:@selector(Session:didReceivedBudStatus:)])
	{
		[theDelegate Session:self didReceivedBudStatus:buddyID];
	}
}
// Process all Buddies status
-(void)receiveBudStatList:  (YMSG9Packet*)pkt			//0xf0
{
	
	NSString* buddyID = [NSString alloc];
	YahooUser* u = [[YahooUser alloc]init];
	NSInteger stat = 0;
	NSMutableArray* usersList = [[NSMutableArray alloc] init];
	int count = [pkt.body count];
	for (int i = 0; i < count; i+=2 ) {
		if ((i+1) > count)
			break;
		switch ([[pkt.body objectAtIndex:i] intValue]) {
			case 7: // buddy ID
				buddyID = [pkt.body objectAtIndex:i+1];
				break;
			case 10: // status - ...hic I am tired. Today is 7/10/2009
				stat = [[pkt.body objectAtIndex:i+1] intValue];
				if (stat != 99) {
					YahooUser* u = [[YahooUser alloc]init:buddyID];
					u.status = stat;
					[usersList addObject:u];
				}
				break;
			case 19: //status msg
				u = [[YahooUser alloc]init:buddyID];
				u.status = stat;
				u.customStatusMessage = [pkt.body objectAtIndex:i+1];
				[usersList addObject:u];
				break;
			default:
				break;
		}
	}
	for (int i = 0; i < [usersList count]; i++ ) {
		for (int j=0; j<[self.groups count]; j++) {
			if ([[self.groups objectAtIndex:j] isKindOfClass:[YahooUser class]]) {
				if ([[self.groups objectAtIndex:j] yahooId] == [[usersList objectAtIndex:i] yahooId]) {
					[self.groups replaceObjectAtIndex:j withObject:[usersList objectAtIndex:i]];
				}
			}
		}
	}
	if([theDelegate respondsToSelector:@selector(Session:didReceivedBuddyList:)])
	{
		[theDelegate Session:self didReceivedBuddyList:YES];
	}
}
// -----------------------------------------------------------------
// Note: the term 'packet' here refers to a YMSG message, not a TCP packet
// (although in almost all cases the two will be synonymous).  This is to
// avoid confusion with a 'YMSG message' - the actual discussion packet.
//
// service - the Yahoo service number
// status - the Yahoo status number (not sessionStatus above!)
// body - the payload of the packet
// -----------------------------------------------------------------
-(void)sendPacket:(PacketBodyBuffer*)body service:(UInt16)service status:(UInt32)s
{	
	[self sendPacket:body service:service status:s sessionId:sessionId];
}

-(void)sendPacket:(PacketBodyBuffer*)body service:(UInt16)service
{	
	[self sendPacket:body service:service status:STATUS_AVAILABLE];
}
-(void)sendPacket:(PacketBodyBuffer*)body service:(UInt16)service status:(UInt32)s sessionId:(UInt32)si {
	NSMutableData* b = [[NSMutableData alloc] init];
	// -----20 byte header
	[b appendData:[NSData dataWithBytes:MAGIC length:4]]; // Magic code 'YMSG'
	[b appendData:[NSData dataWithBytes:VERSION length:4]];
	// convert Int to network byte order. NSData
	// Fucking Iphone. it is little endian.
	UInt16 const g1[1] = {CFSwapInt16HostToBig((UInt16)[body.baos length])}; // Body length (16 bit unsigned)
	
	[b appendData:[NSData dataWithBytes:g1 length:2]];
	UInt16 const g2[1] = {CFSwapInt16HostToBig(service)};
	
	[b appendData:[NSData dataWithBytes:g2 length:2]]; // Service ID (16 bit unsigned)
	UInt32 const g3[1] = {CFSwapInt32HostToBig(s)};
	[b appendData:[NSData dataWithBytes:g3 length:4]]; // Status (32 bit unsigned)
	UInt32 const g4[1] = {CFSwapInt32HostToBig(si)};
	[b appendData:[NSData dataWithBytes:g4 length:4]]; // SessionId (32 bit unsigned)
	// -----Then the body...
	[b appendData:body.baos];
	NSData* c = b;
	// -----Now send the buffer
	[self _writeNextData:c];
	//[b release];
	//[c release];
	
}
-(void) _writeNextData:(NSData*) data {
	//MVAssertCorrectThreadRequired( [network connectionThread] );
	[network writeData:data withTimeout:-1 withTag:0];
}
// -----------------------------------------------------------------
// Convenience method - have we passed a given time?  (More readable)
// -----------------------------------------------------------------
-(BOOL)past:(long long)time
{	
	return ([NSDate timeIntervalSinceReferenceDate]>time);
}

-(void)closeNetwork{	
	if(self.pingThread!=nil) 
	{ 
		self.pingThreadQuit = YES;  
		[self.pingThread cancel]; 
	}
	if(self.network!=nil) {
		@try { 
			[self.network disconnect];  
			self.network=nil; 
		}
		@catch(NSException* e) {
			@throw e;
		}
	}
}
-(void)checkStatus
{	
}

// -----------------------------------------------------------------
// Identities array utility code
// -----------------------------------------------------------------
-(YahooIdentity*)identityIdToObject:(NSString*)yid {	
	for(NSInteger i=0;i<[self.identities count];i++)
		if([yid isEqualToString:[[identities objectAtIndex:i] yahooId]])
			return [identities objectAtIndex:i];
	return nil;
}
-(void) checkIdentity:(YahooIdentity*)yid
{	
	for(int i=0;i<[self.identities count];i++)
		if(yid==[identities objectAtIndex:i])  return;
	NSException* exception = [NSException exceptionWithName:@"IllegalIdentityException" reason:@"not a valid identity for this session" userInfo:nil];
	@throw exception;
}
// FIX: 
-(void)checkIdentityNotOnList:(NSArray*)yids {	
	for(int i=0;i<[yids count];i++)
	{	
		if([self identityIdToObject:[yids objectAtIndex:i]] != nil) {
			NSException* e = [NSException exceptionWithName:@"IllegalIdentityException" reason:[NSString stringWithFormat:@"%@is an identity of this session and cannot be used here",[yids objectAtIndex:i]] userInfo:nil];
			@throw e;
		}
	}				
}
// -----------------------------------------------------------------
// Preform a clean up of all data fields to 'reset' instance
// -----------------------------------------------------------------
-(void)resetData {	
	self.primaryID = nil;  
	self.loginID   = nil;  
	self.password  = nil;
	self.cookieY   = nil;  
	self.cookieT   = nil; 
	//self.cookieC   = nil;
	self.customStatusMessage = nil;  
	self.customStatusBusy    = NO;
	self.groups = nil;  
	self.identities = nil;
	//clearTypingNotifiers();
	self.loginOver = NO;  
}

-(YMSG9Packet*)compoundChatLoginPacket:(YMSG9Packet*)pkt
{	
	if(pkt.status==STATUS_INCOMPLETE) {	
		if(cachePacket==nil)				// First of multiple
		{	
			cachePacket=pkt;
		} else								// 2nd...final-1 of multiple	
		{	
			[cachePacket append:pkt];
		}
		return nil;
	} else if(pkt.status==STATUS_COMPLETE)
	{	
		if(cachePacket!=nil)				// Final of multiple
		{	
			[cachePacket append:pkt];
			pkt=cachePacket;  
			cachePacket=nil;
		}
		//else { NOP }						// Final and only
		return pkt;
	} else	// Should never happen!
	{		
		return pkt;
	}
}

// -----------------------------------------------------------------
// LOGON packets can contain multiple friend status sections,
// ISAWAY and ISBACK packets contain only one.  Update the YahooUser
// -----------------------------------------------------------------
-(void) updateFriendsStatus:(YMSG9Packet*)pkt
{	
	// -----Online friends count, however count may be missing if == 1
	// -----(Note: only LOGON packets have multiple friends)
	NSString* s = [pkt getValue:@"8"];
	if(s==nil && [pkt getValue:@"7"]!=nil)  
		s = @"1";
	// -----If LOGOFF packet, the packet's user status is wrong (available)
	BOOL logoff = (pkt.service == SERVICE_LOGOFF);
	// -----Process online friends data
	if(s!=nil)
	{	
		int cnt = [s integerValue];
		// -----Process each friend
		for(int i=0;i<cnt;i++)
		{	// -----Update user (do not create new user, as client may
			// -----still have reference to old
			YahooUser* yu = [self.userStore get:[pkt getNthValue:@"7" n:i]];
			// -----When we add a friend, we get a status update before
			// -----getting a confirmation FRIENDADD packet (crazy!)
			if(yu==nil)
			{	
				NSString* n = [pkt getNthValue:@"7" n:i];
				yu = [self.userStore getOrCreate:n];
			}
			if([pkt exists:@"17"])
			{	
				[yu update:[pkt getNthValue:@"7" n:i]
						st:(logoff ? [NSString stringWithFormat:@"%i",STATUS_OFFLINE] : [pkt getNthValue:@"10" n:i])
						ch:[pkt getNthValue:@"17" n:i]
					   pig:[pkt getNthValue:@"13"n:i]
				 ];
			}
			else {	
				[yu update:[pkt getNthValue:@"7" n:i]
						st:(logoff ? [NSString stringWithFormat:@"%i",STATUS_OFFLINE] : [pkt getNthValue:@"10" n:i])
						vs:[pkt getNthValue:@"13" n:i]
				 ];
			}
			// -----Custom message?
			if([pkt getNthValue:@"19" n:i]!=nil && [pkt getNthValue:@"47" n:i]!=nil)
			{	
				[yu setCustom:[pkt getNthValue:@"19" n:i] a:[pkt getNthValue:@"47" n:i]];
			}
			//[se setUser:i yu:yu];
		}
	}
}

-(YMSG9Packet*)receivePacket: (NSData*)readData{
	if ([readData length]<= 0) 
		return nil;
	YMSG9Packet* p = [[YMSG9Packet alloc] init];
	UInt8 header[20];
	[readData getBytes:header length:20];	
	// -----Somewhat ineligant way to extract the header data? Fucking Iphone
	p.magic  = [NSString stringWithFormat:@"%c%c%c%c",header[0], header[1],header[2],header[3]];
	p.version = (UInt32)header[5];
	p.length = ((UInt16)header[8]<<8)+((UInt16)header[9]);
	p.service = ((UInt16)header[10]<<8)+((UInt16)header[11]);
	p.status = ((UInt32)header[12]<<24)+((UInt32)header[13]<<16)+((UInt32)header[14]<<8)+((UInt32)header[15]);
	p.sessionId = ((UInt32)header[16]<<24)+((UInt32)header[17]<<16)+((UInt32)header[18]<<8)+((UInt32)header[19]);
	// -----Check the header
	
	if(![p.magic isEqualToString:(@"YMSG")]) {
		NSException *exception = [NSException exceptionWithName:@"Bad YMSG9" reason:@"TBad YMSG9 header"  userInfo:nil];
		@throw exception;
	}
	// -----Read the body
	UInt8 v[[readData length]]; 
	[readData getBytes:v]; 
	// -----Now extract strings in the body
	NSInteger start=20;
	NSString* s = [[NSString alloc] init];
	NSData* w = [[NSData alloc] init];
	@try {
		for(NSInteger i=20;i<p.length+19;i++) {
			
			if(v[i]==0xc0 && v[i+1]==0x80) {	// -----Create a UTF-8 string and add to array
				//NSRange* r = [[NSRange alloc] ];
				
				
				w = [readData subdataWithRange:(NSRange){start, i-start}];
				s = [[NSString alloc] initWithData:w encoding:NSUTF8StringEncoding];
				NSLog(@"String: %@", s);
				[p.body addObject:s];
				// -----Skip over second byte in separator, reset string start
				i++; 
				start=i+1;
			}
			
		}
	}@catch (NSException* e) {
	}
	return p;
	
}
- (void) directClientConnection:(MVDirectClientConnection *) connection didReadData:(NSData *) data withTag:(long) tag {
	[self _readNextMessage];
	@try {
		YMSG9Packet* pkt = [self receivePacket:data];
		[self inputThreadProcess:pkt];
	} 
	@catch (NSException* e) {
		NSLog(@"exception, reason: %@", [e reason]);
	}
	
}
- (void) directClientConnection:(MVDirectClientConnection *) connection didWriteDataWithTag:(long) tag {
	NSLog(@"anhyeuem");
	[self _readNextMessage];
}
- (void) _readNextMessage {
	//MVAssertCorrectThreadRequired( [network connectionThread] );	
	[network readDataWithTimeout:-1 withTag:0];
}		
// -----Switch on packet type to handler code
-(void) inputThreadProcess:(YMSG9Packet*)pkt
{	
	
	if(pkt.sessionId!=0)					// Some chat packets send zero
		self.sessionId = pkt.sessionId;		// Update sess id in outer class
	// -----Error header?
	NSLog(@"service: %x", pkt.service);
	if((pkt.status==-1) && ([self inputThreadProcessError:pkt]==YES)) {
		return;
	}
	// -----Process payload
	const UInt16 sv = pkt.service;
	switch(sv)				// Jump to service-specific code
	{	 
		case SERVICE_AUTH : 		
			[self receiveAuth:pkt];  
			break;
		case SERVICE_ACCINFO :
			[self receiveAccInfo:pkt];  
			break;
		case SERVICE_CHATPM :		
			[self receiveChatPM:pkt];  
			break;  
		case SERVICE_IDACT :		
			[self receiveIdAct:pkt];  
			break;
		case SERVICE_LIST : 		
			[self receiveList:pkt];  
			break;
		case SERVICE_LOGOFF :		
			[self receiveLogoff:pkt];  
			break;
		case SERVICE_LOGON :		
			[self receiveLogon:pkt];  
			break;
		case SERVICE_MESSAGE :		
			[self receiveMessage:pkt];  
			break;
		case SERVICE_PING :			
			[self receivePing:pkt];  
			break;
		case SERVICE_USERSTAT :		
			[self receiveUserStat:pkt];  
			break;
		case SERVICE_BUDSTAT:	
			[self receiveBudStat:pkt];  
			break;
		case SERVICE_BUDSTATLIST:
			[self receiveBudStatList:pkt];
			break;
		default : 
			NSLog(@"UNKNOWN: %@", [pkt toString]);  
			break;
	}
}

// -----Called when status == -1.  Returns true if no further processing is
// -----required (process() returns) otherwise false (process() continues).
-(BOOL)inputThreadProcessError:(YMSG9Packet*) pkt
{	
	switch(pkt.service)				// Jump to service-specific code
	{	
		case SERVICE_ACCINFO :		
			[self receiveAccInfo:pkt];  
			return YES;
		case SERVICE_LOGOFF :		
			[self receiveLogoff:pkt];  
			return YES;
		default : 
			//errorMessage(pkt,null);  
			return ([pkt.body count] <=2);
	}
}
// -----------------------------------------------------------------
// Start threads
// -----------------------------------------------------------------
-(void)startThreads {	
	self.pingThread = [[NSThread alloc] initWithTarget:self selector:@selector(pingThreadProcess) object:nil];
	[self.pingThread start];
}
-(void)pingThreadProcess {
	@try { 
		[NSThread sleepForTimeInterval:PING_TIMEOUT]; 
	} 
	@catch(NSException* e) {
		NSLog(@"exception reason:", [e reason]);
	}
	while(!self.pingThreadQuit)
	{	
		@try
		{	
			[self transmitPing];
			@try { 
				[NSThread sleepForTimeInterval:PING_TIMEOUT]; 
			} 
			@catch(NSException* e) {
				NSLog(@"exception reason:", [e reason]);
			}
		}
		@catch(NSException* e) {
			NSLog(@"exception reason:", [e reason]);
		}
	}
}
-(void)receiveChatPM:(YMSG9Packet*)pkt			// 0x20
{	
	@try
	{	
		if([theDelegate respondsToSelector:@selector(Session:didReceivedPM:to:msg:)])
		{
			[theDelegate Session:self didReceivedPM:[pkt getValue:@"4"] to:[pkt getValue:@"5"] msg:[pkt getValue:@"14"]];
		}
	}
	@catch(NSException* e) {
		NSException* e2 = [NSException exceptionWithName:@"YMSG9BadFormatException" reason:@"chat PM" userInfo:nil];
		@throw e2;
	}
}
// -----------------------------------------------------------------
// Process an incoming AUTH packet (in response to the AUTH packet
// we transmitted to the server).
// Format: "1" <loginID> "94" <challenge string (24 chars)>
// Note: for YMSG10 Yahoo sneakily changed the challenge/response
// method dependant upon a switch in field '13'.  If this field
// is 0 use v9, if 1 then use v10.
// -----------------------------------------------------------------
-(void) receiveAuth:(YMSG9Packet*)pkt 			// 0x57
{	
	NSString* chal = [pkt getValue:@"94"];
	NSString* crumb = [self getCRUMB:chal];
	if (crumb == nil) {
		if([theDelegate respondsToSelector:@selector(Session:errorLogin:)])
		{
			[theDelegate Session:self errorLogin:@"Yahoo! ID or password is incorrect. Please try again"];
		}
		return;
	}
	NSString* crumbChal = [NSString stringWithFormat:@"%@%@", crumb, chal];
	NSString* s = [NSString alloc];
	@try {	
		s = [ChallengeResponseUtility y64md5:crumbChal];
	} @catch( NSException* e) { 
		@throw e; 
	}
	[self transmitAuthResp:s];
}
// -----------------------------------------------------------------
// Process an incoming AUTHRESP packet.  If we get one of these it
// means the logon process has failed.  Set the session state to be
// failed, and flag the end of login.
// Note: we don't throw exceptions on the input thread, but instead
// we pass them to the thread which called login()
// -----------------------------------------------------------------
-(void)receiveAccInfo:(YMSG9Packet*)pkt			// 0xc6
{	
	self.sessionStatus= AUTH;  
	self.loginOver=YES;
	[self startThreads];
}
-(NSString*)getToken:(NSString*) chal{
	NSString* urlStr = [NSString stringWithFormat:@"https://login.yahoo.com/config/pwtoken_get?src=ymsgr&ts=&login=%@&passwd=%@&chal=", self.loginID, self.password, chal];
	NSURL *url = [NSURL URLWithString:urlStr];
	NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:30.];
	NSData *result = [NSURLConnection sendSynchronousRequest:request returningResponse:NULL error:NULL];
	NSString* address = [[[NSString allocWithZone:nil] initWithData:result encoding:NSASCIIStringEncoding] autorelease];
	NSLog(@"%@", address);
	if (address !=nil && [address length] > 10) {	
		NSString* ymsgr	= [address substringFromIndex:[address rangeOfString:@"ymsgr="].location+6];
		ymsgr	= [ymsgr substringToIndex:[ymsgr rangeOfString:@"partnerid="].location-2];
		return ymsgr;
	} else {
		return nil;
	}
}
-(NSString*) getCRUMB:(NSString*) chal {
	NSString* token = [self getToken:(NSString*) chal];
	if ([token length] <=10) {
		return nil;
	}
	NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"https://login.yahoo.com/config/pwtoken_login?src=ymsgr&ts=&token=%@", token]];
	NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:30.];
	NSData *result = [NSURLConnection sendSynchronousRequest:request returningResponse:NULL error:NULL];
	NSString* cookies = [[[NSString allocWithZone:nil] initWithData:result encoding:NSASCIIStringEncoding] autorelease];
	if (cookies != nil && [cookies length] > 10) {
		NSString* crumb	= [cookies substringFromIndex:[cookies rangeOfString:@"crumb="].location+6];
		crumb	= [crumb substringToIndex:[crumb rangeOfString:@"Y="].location-2];
		self.cookieY	= [cookies substringFromIndex:[cookies rangeOfString:@"Y="].location+2];
		self.cookieY	= [self.cookieY substringToIndex:[self.cookieY rangeOfString:@"; path=/;"].location];
		self.cookieT	= [cookies substringFromIndex:[cookies rangeOfString:@"T="].location+2];
		self.cookieT	= [self.cookieT substringToIndex:[self.cookieT rangeOfString:@"; path=/;"].location];
		return crumb;
	} else {
		return nil;
	}
}
@end
